/********************************************************************

  @file   Tuner_Proto.c
  @author Jon Zeeff (jon@zeeff.com)
  @date   September, 2011
  @brief  Program to partially implement the MegaTune serial protocol for sending and receiving engine control parameters.
  @warning Lots of commands not implemented yet
  @copyright MIT License
 
  Written as part of the Open5xxxECU project

********************************************************************** */

/*
Copyright (c) 2011 Jon Zeeff
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include "config.h"
#include "system.h"
#include <stdint.h>
#include "OS.h"
#include "main.h"
#include "eSCI_DMA.h"
#include "FLASH_OPS.h"
#include "variables.h"
#include "crc.h"
#include <string.h>
#include <stdio.h>

int32_t make_packet(uint8_t code, const void *buf, uint16_t size);
uint32_t Crc32_ComputeBuf(uint32_t inCrc32, const void *buf, uint32_t bufLen);
void tuner_task(void);
int32_t process_packet(const uint8_t * buf, uint16_t size);
void _start(void);

/*  Protocol notes:

<command> <canid> <page> <16bit offset> <16bit size> <data....>

Send (to ECU):
<16bit payload size> <existing command> <CRC32>
(Size is total size of packet excluding wrapper)

Receive (to tuner):
<16bit payload size> <8 bit type> <optional data> <CRC32>
payload size includes the type+data but not CRC
CRC includes type+data but not size

*/

// response codes

#define OK 0x00
#define realtime_data 0x01
#define page_data 0x02
#define config_error 0x03
#define burn_ok 0x04
#define page10_ok 0x05
#define CAN_data 0x06
        // Errors
#define underrun 0x80
#define over_run 0x81
#define CRC_failure 0x82
#define unrecognized_command 0x83
#define out_of_range 0x84
#define busy 0x85
#define flash_locked 0x86
#define sequence_failure_1 0x87
#define sequence_failure_2 0x88
#define CAN_queue_full 0x89
#define CAN_timeout 0x8a
#define CAN_failure 0x8b

/*

Example with crc:
Time: 3:29.052: SENT, 7 bytes
x00 x01 x41 xD3 xD9 x9E x8B                                           ..A....
Time: 3:29.073: Received, 3 bytes
x01 x7D x01                                                           .}.
Time: 3:29.277: Received, 384 bytes
x04 x70 x06 x89 x06 x89 x09 xCB x01 x43 x00 x01 x93 x93 x01 x01       .p.......C......

*/

#define CANid 0

/**
 * @name    tuner_task
 * @brief   processes serial commands from tuner
 *
 * This API provides certain actions as an example.
 *
 * @param [in] repeat  Number of times to do nothing.
 *
 * @retval TRUE   Successfully did nothing.
 * @retval FALSE  Oops, did something.
 */

// this buffer is used to receive and send packets

static uint8_t tmp_buf[SERIAL_BUFFER_SIZE];

void tuner_task(void)
{
    static uint_fast16_t count;
    static uint_fast16_t i;

    task_open();                // standard OS entry - required on all tasks

    for (;;) {
        task_wait(1);           // let other tasks run
        count = 0;

        // wait until outbound serial port is not busy
        while (write_serial_busy())
            task_wait(111);

        for (;;) {              // loop until we get a packet (delimited by no activity)
            i = read_serial(tmp_buf + count, sizeof(tmp_buf) - count);  // get block of bytes from serial port

            if (i == 0 && count > 0)    // we received nothing in the last n msec - process the packet
                break;

            count += i;         // keep receiving
            task_wait(99);
        }                       // for

        static uint32_t crc;
#if 0
        // stuff for CRC, new error checked packets
        // get size of data from the packet (doesn't include size or crc)
        size = *(uint16_t *) tmp_buf;
        if (count > 2048)
            return make_packet(over_run, "", 0);

        // verify packet crc
        crc = *(uint32_t *) (buf + 2 + size);
        if (crc != Crc32_ComputeBuf(0, buf + 2, size))
            return make_packet(CRC_failure, "", 0);

        if (buf[2] > 128)       // sanity check command
            return make_packet(out_of_range, "", 0);

#endif
        // process packet

#define PAGE_OFFSET 4
        static uint16_t page;
        static uint16_t offset;
        static uint16_t length;

        switch (tmp_buf[0]) {   // first byte is command
        case 'a':
        case 'A':              //  send real-time variables*
            write_serial((const void *)&Output_Channels, Output_Channels_Size);
            break;
        case 'S':              // ECU version/revision
            write_serial((const void *)version, sizeof(version));
            break;
        case 'Q':              // signature
            write_serial((const void *)signature, sizeof(signature));
            break;
        case 'W':              // write a byte to arbitrary memory location  - W 0xffffffff 0xff
            {
                uint8_t *address;
                uint8_t *ptr;
                uint8_t value;
                uint8_t string[50];
                address = (uint8_t *) atoi(tmp_buf + 1);
                ptr = strchr(tmp_buf + 2, ' ');
                if (!ptr)
                    break;
                value = (uint8_t) atoi(ptr);
                *address = value;
                sprintf(string, "Address %x set to %x\r\n",
                        (uint32_t) address, value);
                write_serial((const void *)string,
                             (uint_fast16_t) strlen(tmp_buf));
                break;
            }
        case 'R':              // read arbitrary memory location - R 0xffffffff
            {
                uint8_t *address;
                uint8_t string[50];
                address = (uint8_t *) atoi(tmp_buf + 1);
                sprintf(string, "Address %x = %x\r\n",
                        (uint32_t) address, *address);
                write_serial((const void *)string,
                             (uint_fast16_t) strlen(tmp_buf));
                break;
            }
        case 'r':              // read*
            if (count != 7)     // sanity check
                break;

            // find page #
            page = *(uint16_t *) (tmp_buf + 1) - PAGE_OFFSET;
            if (page >= nPages)
                break;
            // find offset
            offset = *(uint16_t *) (tmp_buf + 3);
            // find length
            length = *(uint16_t *) (tmp_buf + 5);

            write_serial(Page_Ptr[page] + offset, length);
            break;

        case 'w':              // write*
            if (count < 7)      // sanity check
                break;

            // find page #
            page = *(uint16_t *) (tmp_buf + 1) - PAGE_OFFSET;
            if (page >= nPages)
                break;
            // find offset
            offset = *(uint16_t *) (tmp_buf + 3);
            // find length
            length = *(uint16_t *) (tmp_buf + 5);
            page = (uint_fast16_t) (tmp_buf[2] - PAGE_OFFSET);

            if (offset + length > sizeof(Ram_Page_Buffer))
                break;          // too big

            // if first write to ram, copy initial data from flash
            if (Page_Ptr[page] != Ram_Page_Buffer) {

                // copy initial data from flash to ram (slow!)
                memcpy(Ram_Page_Buffer, Page_Ptr[page], pageSize[page]);
                task_wait(1);   // let other tasks run

                // TODO - check for Ram_Page_Buffer already in use 
                // move ptr from flash to ram - puts new data into use
                Page_Ptr[page] = Ram_Page_Buffer;
            }
            // copy new data to ram page buffer
            memcpy(Ram_Page_Buffer + offset, tmp_buf + 7, length);

            // send response
            make_packet(OK, "", 0);
            break;

        case 'b':              // burn flash*
            if (count != 3)     // sanity check
                break;

            // find page #
            page = *(uint16_t *) (tmp_buf + 1) - PAGE_OFFSET;
            if (page >= nPages)
                break;

            // currently we ignore the page # and burn everything

            // burn flash
            static uint8_t new_flash_block;
            static uint16_t flash_index;

            // select unused new flash block (ping pongs 0 or 1)
            new_flash_block = (Flash_Block == 0) ? 1 : 0;

            flash_index = 1024; // leave room for headers

            // write each page to new flash (8 byte alignment, attend to other tasks every 8 bytes)

            // for each page, write to new flash and burn
            for (i = 0; i < nPages; ++i) {

                if (Flash_Moved[i]) {   // this flash page was written earlier (not implemented)
                    Flash_Moved[i] = 0;
                    continue;   // move to next one
                }

                static uint8_t *ptr;
                ptr = Page_Ptr[i];      // current values, usually in old flash, could be ram
                count = pageSize[i];

                // write 8 bytes at a time 
                while (count > 0) {
                    while (!Flash_Ready())
                        task_wait(1);   // run other tasks

                    Flash_Program(new_flash_block, (unsigned long *)ptr, flash_index);  // setup + copy

                    count -= 8;
                    flash_index += 8;
                    ptr += 8;

                    while (!Flash_Ready())
                        task_wait(1);   // run other tasks
                    Flash_Finish();     // actual burn occurs here

                }               // while

                task_wait(13);  // let other tasks run

            }                   // for

            while (!Flash_Ready())      // make sure flash is ready to use
                task_wait(1);   // run other tasks

            // update all Page_Ptrs to point to new flash
            Flash_Block = new_flash_block;      // 0 or 1
            Set_Page_Locations();       // update pointers to new flash
            // we are now running with all variables in the new flash

            // erase old flash block

            // select previous flash block (ping pongs 0 or 1)
            new_flash_block = (Flash_Block == 0) ? 1 : 0;

            Flash_Erase(new_flash_block);
            while (!Flash_Ready())
                task_wait(117);

            make_packet(burn_ok, 0, 0);
            break;

        case 'k':              // crc32 of page*
            if (count != 7)     // sanity check
                break;
            // find page #
            page = *(uint16_t *) (tmp_buf + 1) - PAGE_OFFSET;
            if (page >= nPages)
                break;
            // find offset
            offset = *(uint16_t *) (tmp_buf + 3);
            // find length
            length = *(uint16_t *) (tmp_buf + 5);

            crc = Crc32_ComputeBuf(0, Page_Ptr[page] + offset, length);
            write_serial((const void *)&crc, sizeof(crc));
            break;

        case 'u':              // reset
        case '!':              // reboot
            // _start();
            break;
        case 'c':              // Clock seconds - 2 bytes
            {
                uint16_t i = (uint16_t) (msec_clock / 1000);
                write_serial((const void *)&i, 2);
            }
            break;
        default:
            make_packet(unrecognized_command, "", 0);
            break;
        }                       // switch

    }                           // for ever

    task_close();
}                               // tuner_task()

// Add byte count to the beginning of a packet and a crc at the end.  Then send the packet.

int32_t make_packet(const uint8_t code, const void *buf, const uint16_t size)
{
#if 0
    *(uint16_t *) tmp_buf = size + 1;   // size - includes code
    *(uint8_t *) (tmp_buf + 2) = code;  // code
    memcpy(tmp_buf + 3, buf, size);     // data
    *(int32_t *) (tmp_buf + 2 + 1 + size) = Crc32_ComputeBuf(0, tmp_buf + 2, size + 1); // CRC on code + data

    write_serial((const void *)tmp_buf, size + 2 + 1 + 4);      // with size, code and crc
#endif

    write_serial((const void *)&code, 1);

    return 0;
}
